Skip to content

fix(bundler): reject non-list 'catalogs' in bundle-catalogs.yml with a clean error#3623

Merged
mnriem merged 2 commits into
github:mainfrom
jawwad-ali:fix/bundler-catalog-config-list-guard
Jul 22, 2026
Merged

fix(bundler): reject non-list 'catalogs' in bundle-catalogs.yml with a clean error#3623
mnriem merged 2 commits into
github:mainfrom
jawwad-ali:fix/bundler-catalog-config-list-guard

Conversation

@jawwad-ali

Copy link
Copy Markdown
Contributor

What

_merge_config (the source-stack loader for bundle-catalogs.yml) guards only the falsy case:

catalogs = data.get("catalogs") if isinstance(data, dict) else None
if not catalogs:
    return
for raw in catalogs:   # <-- raw TypeError if `catalogs` is a non-empty scalar

A non-empty scalar like catalogs: 5 (or catalogs: true) passes the falsy check and then raises a raw TypeError: 'int' object is not iterable from the loop — escaping the module's BundlerError error contract.

The sibling reader of the same filecommands_impl/catalog_config.py (used by specify bundle catalog list) — already raises an actionable BundlerError("Malformed catalog config … 'catalogs' must be a list …") for the identical mis-shape. So the two readers of bundle-catalogs.yml diverge.

Fix

Add the same isinstance(catalogs, list) guard, raising the same-shaped BundlerError. One file, ~3 lines (BundlerError already imported). Matches the shape-guard vein used across catalogs.py, workflows/catalog.py, presets, and extensions.

Test

catalogs: 5 now raises BundlerError(…must be a list…) — fails before (raw TypeError), passes after. Full test_catalog_schema.py (15) passes.


🤖 Written with the assistance of Claude Code (AI). Bug self-found; fix/tests verified locally (fail-before / pass-after), ruff clean.

…a clean error

_merge_config guarded only 'if not catalogs: return', so a non-empty scalar
(catalogs: 5) passed through and raised a raw 'TypeError: int object is not
iterable' from the loop below — escaping the module's BundlerError error
contract. The sibling reader of the same file (commands_impl/catalog_config.py,
used by 'bundle catalog list') already raises an actionable BundlerError for the
identical mis-shape. Add the same isinstance(list) guard so both readers of
bundle-catalogs.yml agree.

Test: 'catalogs: 5' raises BundlerError('...must be a list...') (fails before:
raw TypeError).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds validation so malformed bundle catalog configuration raises BundlerError.

Changes:

  • Validates that catalogs is a list.
  • Adds a regression test for catalogs: 5.
  • One issue remains: falsy non-list values bypass validation.
Show a summary per file
File Description
src/specify_cli/bundler/models/catalog.py Adds catalog type validation.
tests/contract/test_catalog_schema.py Tests a non-list scalar value.

Review details

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 2/2 changed files
  • Comments generated: 1
  • Review effort level: Medium

Comment thread src/specify_cli/bundler/models/catalog.py Outdated

@mnriem mnriem left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please address Copilot feedback

Address review: the isinstance guard sat after `if not catalogs: return`,
so falsy non-list values (`catalogs: false`, `0`, `''`, `{}`) hit the
early return and were silently accepted instead of raising the promised
BundlerError. Only an absent/None value means "nothing to merge".

Change the early return to `if catalogs is None`, mirroring the sibling
reader (commands_impl/catalog_config._read). An empty list stays valid
(the merge loop is a no-op). Add parametrized tests for the falsy
non-list cases and for the absent/empty-list no-op.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@jawwad-ali

Copy link
Copy Markdown
Contributor Author

Fixed in 094f1bf: changed the early return to if catalogs is None so only an absent value is treated as 'nothing to merge'. Every other non-list value — including the falsy ones (false, 0, '', {}) — now raises the BundlerError, matching the sibling reader. Added parametrized tests for the falsy non-list cases and for the absent/empty-list no-op.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review details

  • Files reviewed: 2/2 changed files
  • Comments generated: 0 new
  • Review effort level: Medium

@mnriem
mnriem self-requested a review July 22, 2026 13:49
@mnriem
mnriem merged commit 3b9deec into github:main Jul 22, 2026
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants